package com.zrkizzy.seckill.service.core.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zrkizzy.seckill.entity.Order;
import com.zrkizzy.seckill.entity.SeckillGoods;
import com.zrkizzy.seckill.entity.SeckillOrder;
import com.zrkizzy.seckill.entity.User;
import com.zrkizzy.seckill.enums.HttpStatusEnum;
import com.zrkizzy.seckill.enums.OrderChannelEnum;
import com.zrkizzy.seckill.enums.OrderStatusEnum;
import com.zrkizzy.seckill.exception.GlobalException;
import com.zrkizzy.seckill.mapper.OrderMapper;
import com.zrkizzy.seckill.mapper.SeckillGoodsMapper;
import com.zrkizzy.seckill.mapper.SeckillOrderMapper;
import com.zrkizzy.seckill.service.core.IGoodsService;
import com.zrkizzy.seckill.service.core.IOrderService;
import com.zrkizzy.seckill.service.core.ISeckillGoodsService;
import com.zrkizzy.seckill.service.tool.IRedisService;
import com.zrkizzy.seckill.utils.*;
import com.zrkizzy.seckill.vo.system.GoodsVO;
import com.zrkizzy.seckill.vo.system.OrderDetailVO;
import com.zrkizzy.seckill.vo.system.OrderVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

import static com.zrkizzy.seckill.constant.RedisConst.*;
import static com.zrkizzy.seckill.constant.TimeConst.ONE_MINUTE;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author zhangrongkang
 * @since 2023-03-22
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
    @Autowired
    private IGoodsService goodsService;
    @Autowired
    private ISeckillGoodsService seckillGoodsService;
    @Autowired
    private IRedisService redisService;

    @Autowired
    private SnowFlakeUtil snowFlakeUtil;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private SeckillOrderMapper seckillOrderMapper;

    /**
     * 商品秒杀逻辑
     *
     * @param user  用户信息对象
     * @param goods 商品详情
     * @return 订单信息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Order secKill(User user, GoodsVO goods) {
        // 获取到秒杀商品的信息
        SeckillGoods seckillGoods = seckillGoodsMapper.selectOne(
                new QueryWrapper<SeckillGoods>().eq("goods_id", goods.getId()));
        // 更新当前秒杀商品信息并获取秒杀结果
        seckillGoodsService.update(new UpdateWrapper<SeckillGoods>()
                .setSql("stock_count = stock_count - 1")
                .eq("id", seckillGoods.getId())
                .gt("stock_count", 0));
        // 判断秒杀商品库存
        if (seckillGoods.getStockCount() < 1) {
            // 将当前商品添加到Redis中，表示当前商品没有库存
            redisService.set(IS_STOCK_EMPTY + goods.getId(), goods.getGoodsName(), TimeUtil.secondIntervalNow(goods.getEndTime()));
            return null;
        }

        // 生成订单信息
        Order order = Order.builder()
                .id(snowFlakeUtil.nextId())
                .userId(user.getId())
                .goodsId(goods.getId())
                .deliveryAddrId(0L)
                .goodsName(goods.getGoodsName())
                .goodsPrice(seckillGoods.getSeckillPrice())
                .goodsCount(1)
                .orderChannel(OrderChannelEnum.PC.getCode())
                .status(OrderStatusEnum.UNPAID.getCode())
                .createTime(LocalDateTime.now())
                .build();
        log.info("订单信息为：{}", order);
        // 添加订单信息到数据库
        orderMapper.insert(order);

        // 生成秒杀订单
        SeckillOrder seckillOrder = SeckillOrder.builder()
                .id(snowFlakeUtil.nextId())
                .userId(user.getId())
                .orderId(order.getId())
                .goodsId(goods.getId())
                .build();
        log.info("秒杀订单信息为：{}", seckillOrder);
        // 保存秒杀订单信息
        seckillOrderMapper.insert(seckillOrder);
        // 将订单信息保存到Redis中，过期时间为秒杀活动结束时
        redisService.set(ORDER_PREFIX + user.getId() + ":" + goods.getId(), seckillOrder, TimeUtil.secondIntervalNow(goods.getEndTime()));
        return order;
    }

    /**
     * 获取订单详情
     *
     * @param orderId 订单ID
     * @return 订单详情返回对象
     */
    @Override
    public OrderDetailVO detail(Long orderId) {
        // 判断订单是否存在
        if (null == orderId) {
            throw new GlobalException(HttpStatusEnum.ORDER_NOT_EXIST);
        }
        // 获取订单数据
        OrderVO orderVO = BeanCopyUtil.copy(orderMapper.selectById(orderId), OrderVO.class);
        log.info("订单返回信息为：{}", orderVO);
        GoodsVO goodsVO = goodsService.getGoodsById(orderVO.getGoodsId());
        return OrderDetailVO.builder()
                .orderVO(orderVO)
                .goodsVO(goodsVO)
                .build();
    }

    /**
     * 获取秒杀接口的地址
     *
     * @param user    用户对象
     * @param goodsId 商品ID
     * @return 前端响应对象
     */
    @Override
    public String createPath(User user, Long goodsId) {
        String path = Md5Util.md5(UUIDUtil.uuid() + "123456");
        // 在Redis中存储秒杀地址，过期时间一分钟
        redisService.set(SECKILL_PATH + user.getId() + ":" + goodsId, path, ONE_MINUTE);
        return path;
    }

    /**
     * 校验秒杀地址
     *
     * @param path 加密后的请求地址
     * @param user    用户对象
     * @param goodsId 商品ID
     * @return 秒杀地址是否正确
     */
    @Override
    public Boolean checkPath(String path, User user, Long goodsId) {
        // 非空校验
        if (null == user || null == goodsId || !StringUtils.hasLength(path)) {
            return Boolean.FALSE;
        }
        // 获取Redis中存储的请求路径并返回比较结果
        return path.equals(redisService.get(SECKILL_PATH + user.getId() + ":" + goodsId, String.class));
    }

    /**
     * 验证验证码
     *
     * @param user    用户对象
     * @param goodsId 商品ID
     * @param kaptcha 验证码
     * @return 验证码是否正确
     */
    @Override
    public Boolean checkKaptcha(User user, Long goodsId, String kaptcha) {
        // 获取到当前用户在Redis中存储的验证码
        String redisKaptcha = redisService.get(KAPTCHA_PREFIX + user.getId() + ":" + goodsId, String.class);
        return kaptcha.equals(redisKaptcha);
    }
}
